[Flutter] ページインジケーター機能を実装する
こんにちは、CX事業本部 Delivery部の若槻です。
Flutterでモバイルアプリを開発しているのですが、アプリインスントール後の初回起動時にユーザーに対してアプリの使い方や目的を説明するためのウォークスルー機能を実装することになり、そのためのUIとしてページインジケーターを採用することになりました。
ページインジケータを実装できるパッケージを探してみたところsmooth_page_indicatorが良さそうでしたので試してみました。
Exampleを動かしてみる
まずは下記のExampleコードを動かしてみます。(基本的にExampleそのままですが、一部Warningを抑制する修正をしています。)
import 'package:flutter/material.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Smooth Page Indicator Demo', theme: ThemeData(primarySwatch: Colors.blue), home: const HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final controller = PageController(viewportFraction: 0.8, keepPage: true); @override Widget build(BuildContext context) { final pages = List.generate( 6, (index) => Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), color: Colors.grey.shade300, ), margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), child: SizedBox( height: 280, child: Center( child: Text( "Page $index", style: const TextStyle(color: Colors.indigo), )), ), )); return Scaffold( body: SafeArea( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ const SizedBox(height: 16), SizedBox( height: 240, child: PageView.builder( controller: controller, // itemCount: pages.length, // ループの制御 itemBuilder: (_, index) { return pages[index % pages.length]; }, ), ), const Padding( padding: EdgeInsets.only(top: 24, bottom: 12), child: Text( 'Worm', style: TextStyle(color: Colors.black54), ), ), SmoothPageIndicator( controller: controller, count: pages.length, effect: const WormEffect( dotHeight: 16, dotWidth: 16, type: WormType.thin, strokeWidth: 5, ), ), const Padding( padding: EdgeInsets.only(top: 16, bottom: 8), child: Text( 'Jumping Dot', style: TextStyle(color: Colors.black54), ), ), SmoothPageIndicator( controller: controller, count: pages.length, effect: const JumpingDotEffect( dotHeight: 16, dotWidth: 16, jumpScale: .7, verticalOffset: 15, ), ), const Padding( padding: EdgeInsets.only(top: 16, bottom: 12), child: Text( 'Scrolling Dots', style: TextStyle(color: Colors.black54), ), ), SmoothPageIndicator( controller: controller, count: pages.length, effect: const ScrollingDotsEffect( activeStrokeWidth: 2.6, activeDotScale: 1.3, maxVisibleDots: 5, radius: 8, spacing: 10, dotHeight: 12, dotWidth: 12, )), const Padding( padding: EdgeInsets.only(top: 16, bottom: 16), child: Text( 'Customizable Effect', style: TextStyle(color: Colors.black54), ), ), SmoothPageIndicator( controller: controller, count: pages.length, effect: CustomizableEffect( activeDotDecoration: DotDecoration( width: 32, height: 12, color: Colors.indigo, rotationAngle: 180, verticalOffset: -10, borderRadius: BorderRadius.circular(24), // dotBorder: const DotBorder( // padding: 2, // width: 2, // color: Colors.indigo, // ), ), dotDecoration: DotDecoration( width: 24, height: 12, color: Colors.grey, // dotBorder: DotBorder( // padding: 2, // width: 2, // color: Colors.grey, // ), // borderRadius: BorderRadius.only( // topLeft: Radius.circular(2), // topRight: Radius.circular(16), // bottomLeft: Radius.circular(16), // bottomRight: Radius.circular(2)), borderRadius: BorderRadius.circular(16), verticalOffset: 0, ), spacing: 6.0, // activeColorOverride: (i) => colors[i], inActiveColorOverride: (i) => colors[i], ), ), const SizedBox(height: 32.0), ], ), ), ), ); } } const colors = [ Colors.red, Colors.green, Colors.greenAccent, Colors.amberAccent, Colors.blue, Colors.amber, ];
Exampleを起動してみると、下記のようなページインジケータが実装できています。
解説
smooth_page_indicatorは、SmoothPageIndicator()
にcontroller
とcount
を渡すことで、ページインジケータを実装することができます。
PageView classで引き受けるPageController classのコントローラーを、SmoothPageIndicator()
でも引き受けることにより、スクロールによるページ送り(scroll offset)に合わせてアニメーションが描画されます。
class _HomePageState extends State<HomePage> { final controller = PageController(viewportFraction: 0.8, keepPage: true); @override Widget build(BuildContext context) { final pages = List.generate( 6, (index) => Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), color: Colors.grey.shade300, ), margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), child: SizedBox( height: 280, child: Center( child: Text( "Page $index", style: const TextStyle(color: Colors.indigo), )), ), )); return Scaffold( body: SafeArea( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ const SizedBox(height: 16), SizedBox( height: 240, child: PageView.builder( controller: controller, // itemCount: pages.length, // ループの制御 itemBuilder: (_, index) { return pages[index % pages.length]; }, ), ), const Padding( padding: EdgeInsets.only(top: 24, bottom: 12), child: Text( 'Worm', style: TextStyle(color: Colors.black54), ), ), SmoothPageIndicator( controller: controller, count: pages.length, effect: const WormEffect( dotHeight: 16, dotWidth: 16, type: WormType.thin, strokeWidth: 5, ), ),
Effectでアニメーションのパターンを指定可能
SmoothPageIndicator()
のeffect
ではページ送り時のアニメーションのパターンを指定できます。Exampleでは、下記のパターンを利用しています。
パターン | 説明 |
---|---|
WormEffect | 蠕虫の動きのようなアニメーション |
JumpingDotEffect | ドットが飛び跳ねるアニメーション |
ScrollingDotsEffect | ドットが水平にスクロールするアニメーション |
CustomizableEffect | カスタマイズ可能なアニメーション |
ソースコードを見ると他にもEffectがあるようですので、表現したいUIに応じて選択してください。
PageViewのループにも対応可能
ExampleではPageView
でitemCount
を未指定だったため、ページ送りのループが有効となっていました。
child: PageView.builder( controller: controller, itemCount: pages.length, // ループの制御 itemBuilder: (_, index) { return pages[index % pages.length]; }, ),
itemCount
を指定することで、ループを無効化することができますが、その場合でもSmoothPageIndicator()
では対応可能です。
おわりに
Flutterでページインジケーター機能の実装を試してみました。
SmoothPageIndicator()
は、PageView
のスクロールに合わせてアニメーションを描画することができるため、ページインジケーターのUIを実装する際に便利なパッケージだと思います。
以上